% ch13.tex
% This work is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 License.
% To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/nz
% or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.

\chapter{Serialización de Objetos en Python}\label{ch:serializacion}

\noindent
Nivel de dificultad:\difllll

\begin{citaCap}
``Desde que vivimos en este apartamento, cada sábado me he levantado a las 6:15, \\
me he preparado un tazón de cereales con leche, \\
me he sentado en este lado de este sofá, he puesto la BBC America, 
y he visto Doctor Who.'' \\
---\emph{Sheldon, La teoría del Big Bang.}\footnote{\href{http://en.wikiquote.org/wiki/The\_Big\_Bang\_Theory\#The\_Dumpling\_Paradox\_.5B1.07.5D}{http://en.wikiquote.org/wiki/The\_Big\_Bang\_Theory\#The\_Dumpling\_Paradox\_.5B1.07.5D}}
\end{citaCap}

\section{Inmersión}

El concepto de la serialización es simple. Tienes una estructura de datos en memoria que quieres grabar, reutilizar o enviar a alguien. ¿Cómo lo haces? Bueno, eso depende de lo que quieras grabar, de cómo lo quieras reutilizar y a quién se lo envías. Muchos juegos te permiten grabar el avance cuando sales de ellos, y continuar en donde lo dejaste cuando los vuelves a cargar (En realidad, esto también lo hacen las aplicaciones que no son de juegos). En estos casos, se necesita almacenar en disco una estructura de datos que almacena ''tu grado de avance hasta el momento'', cuando los juegos se reinician, es necesario volver a cargar estas estructuras de datos. Los datos, en estos casos, sólo se utilizan por el mismo programa que los creó, no se envían por la red ni se leen por nadie más que por el programa que los creó. Por ello, los posibles problemas de interoperabilidad quedan reducidos a asegurar que versiones posteriores del mismo programa pueden leer los datos escritos por versiones previas.

Para casos como estos, el módulo \codigo{pickle} es ideal. Forma parte de la librería estándar de Python, por lo que siempre está disponible. Es rápido, la mayor parte está escrito en C, como el propio intérprete de Python. Puede almacenar estructuras de datos de Python todo lo complejas que se necesite.

¿Qué puede almacenar el módulo \codigo{pickle}?

\begin{itemize}

\item Todos los \emph{tipos de datos} nativos que Python soporta: booleanos, enteros, números de coma flotante, números complejos, cadenas, objetos \codigo{bytes}, arrays de byte y \codigo{None}.

\item Listas, tuplas, diccionarios y conjuntos que contengan cualquier combinación de tipos de dato nativos.

\item Listas, tuplas, diccionarios y conjuntos de datos que contengan cualquier combinación de listas, tiplas, diccionarios y conjuntos conteniendo cualquier combinación de tipos de datos nativos (y así sucesivamente, hasta alcanzar un máximo nivel de anidamiento\footnote{\href{http://docs.python.org/3.1/library/sys.html\#sys.getrecursionlimit}{http://docs.python.org/3.1/library/sys.html\#sys.getrecursionlimit}}).

\item Funciones, clases e instancias de clases (con ciertas limitaciones).

\end{itemize}

Si no es suficiente para ti, el módulo \codigo{pickle} se puede extender. Si estás interesado en la extensibilidad, revisa los enlaces de la sección de \emph{Lecturas recomendadas} al final de este capítulo.

\subsection{Una nota breve sobre los ejemplos de este capítulo}

Este capítulo cuenta una historia con dos consolas de Python. Todos los ejemplos de este capítulo son parte de una única historia. Se te pedirá que vayas pasando de una consola a otra de Python para demostrar el funcionamiento de los módulos \codigo{pickle} y \codigo{json}.

Para ayudarte a mantener las cosas claras, abre la consola de Python y define la siguiente variable:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell = 1
\end{lstlisting}
\end{minipage}

Mantén la ventana abierta. Ahora abre otra consola de Python y define la siguiente variable:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell = 2
\end{lstlisting}
\end{minipage}

A lo largo de este capítulo, utilizaré la variable \codigo{shell} para inidcar en qué consola de Python se ejecuta cada ejemplo.

\section{Almacenamiento de datos a un fichero ``pickle''}

El módulo \codigo{pickle} funciona con estructuras de datos. Vamos a construir una.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> entry = {}
>>> entry['title'] = 'Dive into history, 2009 edition'
>>> entry['article_link'] = 'http://diveintomark.org/' + \
        'archives/2009/03/27/dive-into-history-2009-edition'
>>> entry['comments_link'] = None
>>> entry['internal_id'] = b'\xDE\xD5\xB4\xF8'
>>> entry['tags'] = ('diveintopython', 'docbook', 'html')
>>> entry['published'] = True
>>> import time
>>> entry['published_date'] = \
        time.strptime('Fri Mar 27 22:20:42 2009')
>>> entry['published_date']
time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, 
                 tm_hour=22, tm_min=20, tm_sec=42, 
                 tm_wday=4, tm_yday=86, tm_isdst=-1)
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 1:} Tecléalo en la consolo \#1.

\item \emph{Línea 3:} La idea aquí es construir un diccionario de Python que pueda representar algo que sea útil, como una \emph{entrada de una fuente Atom}. Pero también quiero asegurarme de que contiene diferentes tipos de datos para mostrar el funcionamiento del módulo \codigo{pickle}. No entres demasiado en los valores concretos.

\item \emph{Línea 13:} El módulo \codigo{time} contiene una estructura de datos (\codigo{time\_struct}) que representa un punto en el tiempo (con una precisión de milisegundo) y funcionees que sirven para manipular estructuras de este tipo. La función \codigo{strptime()} recibe una cadena formateada y la convierte en una estructura \codigo{time\_struct}. Esta cadena se encuentra en el formato por defecto, pero puedes controlarlo con los códigos de formato. Para más detalles consulta el módulo \codigo{time}\footnote{\href{http://docs.python.org/3.1/library/time.html}{http://docs.python.org/3.1/library/time.html}}.

\end{enumerate}

Bueno, ya tenemos un estupendo diccionario de Python. Vamos a salvarlo en un fichero.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> import pickle
>>> with open('entry.pickle', 'wb') as f:
...     pickle.dump(entry, f)
... 
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 1:} Seguimos en la consola \#1.

\item \emph{Línea 4:} Utiliza la función \codigo{open()} para abrir un fichero. El modo de apertura es \codigo{'wb'}, de escritura y en binario. Lo envolvemos en una sentencia \codigo{with} para asegurar que el fichero se cierra automáticamente al finalizar su uso.

\item \emph{Línea 5:} La función \codigo{dump()} del módulo \codigo{pickle} toma una estructura de datos serializable de Python y la serializa a un formato binario, específico de Python, y almacena el resultado en el fichero abierto.

\end{enumerate}

Esa última sentencia era muy importante.

\begin{itemize}

\item El módulo \codigo{pickle} toma una estructura de datos Python y la salva a un fichero.

\item Para hacer esto, serializa la estructura de datos utilizando un formato de datos denominado ``el protocolo pickle''.

\item Este protocolo es específico de Python; no existe ninguna garantía de compatibilidad entre lenguajes de programación. Probablemente no puedas abrir el fichero \codigo{entry.pickle} con Perl, PHP, Java u otro lenguaje.

\item No todas las estructuras de datos de Python se pueden serializar con el módulo \codigo{pickle}. El protocolo pickle ha cambiado varias veces para acomodar nuevos tipos de datos que se han ido añadiendo a Python, pero aún tiene limitaciones.

\item Como resultado de estos cambios, no existe garantía de compatibilidad entre diferentes versiones de Python. Las versiones nuevas de Python soportan los formatos antiguos de serialización, pero las versiones viejas de Python no soportan los formatos nuevos (puesto que no soportan los tipos de datos nuevos).

\item A menos que especifiques otra cosa, las funciones del módulo \codigo{pickle} utilizarán la última versión del protocolo pickle. Esto asegura que dispones de la máxima flexibilidad en los tipos de datos que puedes serializar, pero también significa que el fichero resultante no podrá leerse en versiones de Python más antiguas que no soporten la última versión del protocolo.

\item La última versión del protocolo pickle es un formato binario. Asegúrate de abrir el fichero en modo binario o los datos se corromperán durante la escritura.

\end{itemize}

\section{Carga de datos de un fichero ``pickle''}

Ahora cambia a la segunda consola de Python ---la otra, la que no utilizaste para crear el diccionario \codigo{entry}.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
2
>>> entry
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'entry' is not defined
>>> import pickle
>>> with open('entry.pickle', 'rb') as f:
...     entry = pickle.load(f)
... 
>>> entry
{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Dive into history, 2009 edition',
 'tags': ('diveintopython', 'docbook', 'html'),
 'article_link':
 'http://diveintomark.org/archives/2009/03/27/
  dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, 
 tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4, 
 tm_yday=86, tm_isdst=-1),
 'published': True}
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 1:} Consola \#2.

\item \emph{Línea 3:} La variable \codigo{entry} no está definida en esta consola. La definimos en la consola \#1, que se trata de un entorno totalmente separado de éste y tiene su propio estado.

\item \emph{Línea 8:} Se abre el fichero \codigo{entry.pickle} que creamos con la consola \#1. El módulo \codigo{pickle} utiliza un formato binario, por lo que siempre hay que abrir los ficheros de este tipo en modo binario.

\item \emph{Línea 9:} La función \codigo{pickle.load()} toma un objeto \codigo{stream}, lee los datos serializados del stream, crea un nuevo objeto Python, recrea los datos serializados en el nuevo objeto Python y devuelve el objeto.

\item \emph{Línea 11:} Ahora la variable \codigo{entry} contiene un diccionario con las claves y valores que nos son familiares de la otra consola.

\end{enumerate}

El ciclo \codigo{pickle.dump() / pickle.load()} da como resultado una estructura de datos nueva que es igual a la original.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> with open('entry.pickle', 'rb') as f:
...     entry2 = pickle.load(f)
... 
>>> entry2 == entry
True
>>> entry2 is entry
False
>>> entry2['tags']
('diveintopython', 'docbook', 'html')
>>> entry2['internal_id']
b'\xDE\xD5\xB4\xF8'
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 1:} Volvemos a la consola \#1.

\item \emph{Línea 3:} Abrimos el fichero \codigo{entry.pickle}.

\item \emph{Línea 4:} Cargamos los datos serializados en la nueva variable \codigo{entry2}.

\item \emph{Línea 6:} Python confirma que los dos diccionarios, \codigo{entry} y \codigo{entry2}, son iguales. En esta consola, construimos el diccionario almacenado en \codigo{entry} desde cero, creando un diccionario vacío y añadiéndole valores poco a poco. Serializamos el diccionario y lo almacenamos en el fichero \codigo{entry.pickle}. Ahora hemos recuperado los datos serializados desde el fichero y hemos creado una réplica perfecta de la estructura de datos original.

\item \emph{Línea 8:} Igualdad no es lo mismo que identidad. Como he dicho, hemos creado una \emph{réplica perfecta} de los datos originales. Pero son una copia.

\item \emph{Línea 10:} Por razones que aclararé más tarde en el capítulo, he querido mostrar que el valor de la clave \codigo{'tags'} es una tupla, y el valor de la clave \codigo{'internal\_id'} es un objeto \codigo{bytes}.

\end{enumerate}

\section{Serialización con ``pickle'' sin pasar por un fichero}

Los ejemplos de la sección anterior te mostraron cómo serializar un objeto Python directamente a un fichero en disco. Pero ¿qué sucede si no necesitas un fichero? Puedes serializar a un objeto \codigo{bytes} en memoria.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> b = pickle.dumps(entry)
>>> type(b)               
<class 'bytes'>
>>> entry3 = pickle.loads(b)
>>> entry3 == entry        
True
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 3:} La función \codigo{pickle.dumps()} (observa la \codigo{'s'} al final del nombre de la función) realiza la misma serialización que la función \codigo{pickle.dump()}. Pero en lugar de tomar como parámetro un objeto stream y serializar sobre él, simplemente retorna los datos serializados.

\item \emph{Línea 4:} Puesto que el protocolo pickle utiliza un formato de datos binario, la función \codigo{pickle.dumps()} retorna un objeto \codigo{bytes}.

\item \emph{Línea 6:} La función \codigo{pickle.loads()} (de nuevo, observa la \codigo{'s'} al final del nombre de la función) realiza la misma operación que la función \codigo{pickle.load()}. Pero en lugar de tomar como parámetro un objeto stream y leer de él los datos, toma un objeto \codigo{bytes} que contenga datos serializados.

\item \emph{Línea 7:} El resultado final es el mismo: una réplica perfecta del diccionario original.

\end{enumerate}


\section{Los bytes y las cadenas de nuevo vuelven sus feas cabezas}

El protocolo pickle existe desde hace muchos años, y ha madurado a la par que lo ha hecho Python. Por ello, actualmente existen cuatro versiones diferentes del protocolo.

\begin{itemize}

\item Python 1.x tenía dos protocolos, un formato basado en texto (``versión 0'') y un formato binario (``versión 1'').

\item Python 2.3 introdujo un protocolo nuevo (``versión 2'') para tener en cuenta la nueva funcionalidad de clases y objetos en Python. Es un formato binario.

\item Python 3.0 introdujo otro protocolo (``versión 3'') que soporta explícitamente los objetos \codigo{bytes} y arrays de byte. Es un formato binario.

\end{itemize}

Como puedes observar, la diferencia existente entre cadenas de texto y bytes vuelve a aparecer (si te sorprende es que no has estado poniendo atención). En la práctica, significa que mientras que Python 3 puede leer datos almacenados con el protocolo versión 2, Python 2 no puede leer datos almacenados con el protocolo versión 3.

\section{Depuración de ficheros ``pickle''}

¿A qué se parece el protocolo ``pickle''? Vamos a salir un momento de la consola de Python y echarle un vistazo al fichero \codigo{entry.pickle} que hemos creado.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ ls -l entry.pickle
-rw-r--r-- 1 you  you  358 Aug  3 13:34 entry.pickle
you@localhost:~/diveintopython3/examples$ cat entry.pickle
comments_linkqNXtagsqXdiveintopythonqXdocbookqXhtmlq?qX publishedq?
XlinkXJhttp://diveintomark.org/archives/2009/03/27/dive-into-history-
2009-edition
q   Xpublished_dateq
ctime
struct_time
?qRqXtitleqXDive into history, 2009 editionqu.
\end{lstlisting}
\end{minipage}

No ha sido muy útil. Puedes ver las cadenas de texto, pero los otros tipos de dato salen como caracteres ilegibles. Los campos no están delimitados por tabuladores ni espacios. No se trata de un formato que quieras depurar por ti mismo.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
>>> shell
1
>>> import pickletools
>>> with open('entry.pickle', 'rb') as f:
...     pickletools.dis(f)
    0: \x80 PROTO      3
    2: }    EMPTY_DICT
    3: q    BINPUT     0
    5: (    MARK
    6: X        BINUNICODE 'published_date'
   25: q        BINPUT     1
   27: c        GLOBAL     'time struct_time'
   45: q        BINPUT     2
   47: (        MARK
   48: M            BININT2    2009
   51: K            BININT1    3
   53: K            BININT1    27
   55: K            BININT1    22
   57: K            BININT1    20
   59: K            BININT1    42
   61: K            BININT1    4
   63: K            BININT1    86
   65: J            BININT     -1
   70: t            TUPLE      (MARK at 47)
   71: q        BINPUT     3
   73: }        EMPTY_DICT
   74: q        BINPUT     4
   76: \x86     TUPLE2
   77: q        BINPUT     5
   79: R        REDUCE
   80: q        BINPUT     6
   82: X        BINUNICODE 'comments_link'
  100: q        BINPUT     7
  102: N        NONE
  103: X        BINUNICODE 'internal_id'
  119: q        BINPUT     8
  121: C        SHORT_BINBYTES 'xxxx'
  127: q        BINPUT     9
  129: X        BINUNICODE 'tags'
  138: q        BINPUT     10
  140: X        BINUNICODE 'diveintopython'
  159: q        BINPUT     11
  161: X        BINUNICODE 'docbook'
  173: q        BINPUT     12
  175: X        BINUNICODE 'html'
  184: q        BINPUT     13
  186: \x87     TUPLE3
  187: q        BINPUT     14
  189: X        BINUNICODE 'title'
  199: q        BINPUT     15
  201: X        BINUNICODE 'Dive into history, 2009 edition'
  237: q        BINPUT     16
  239: X        BINUNICODE 'article_link'
\end{lstlisting}
\end{minipage}

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
  256: q        BINPUT     17
  258: X        BINUNICODE 'http://diveintomark.org/archives/2009/
                            03/27/dive-into-history-2009-edition'
  337: q        BINPUT     18
  339: X        BINUNICODE 'published'
  353: q        BINPUT     19
  355: \x88     NEWTRUE
  356: u        SETITEMS   (MARK at 5)
  357: .    STOP
highest protocol among opcodes = 3
\end{lstlisting}
\end{minipage}

La información más interesante de este volcado es la que aparece en la última línea, ya que muestra la versión del protocolo de ``pickle'' con la que el fichero se grabó. No existe un marcador de versión explício en el protocolo de ``pickle''. Para determinar la versión del protocolo, se observan los marcadores (códigos de operación - ``opcodes'') existentes en los datos almacenados y se utiliza el conocimiento expreso de qué códigos fueron introducidos en cada versión del protocolo ``pickle''. La función \codigo{pickle.dis()} hace exactamente eso e imprime el resultado en la última línea del volcado de salida.

La siguiente es una función que simplemente devuelve el número de versión sin imprimir nada:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
import pickletools

def protocol_version(file_object):
    maxproto = -1
    for opcode, arg, pos in pickletools.genops(file_object):
        maxproto = max(maxproto, opcode.proto)
    return maxproto
\end{lstlisting}
\end{minipage}

Y aquí la vemos en acción:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> import pickleversion
>>> with open('entry.pickle', 'rb') as f:
...     v = pickleversion.protocol_version(f)
>>> v
3
\end{lstlisting}
\end{minipage}

\section{Serialización de objetos Python para cargarlos en otros lenguajes}

El formato utilizado por el módulo \codigo{pickle} es específico de Python. No intenta ser compatible con otros lenguajes de programación. Si la compatibilidad entre lenguajes es un requisito, necesitas utilizar otros formatos de serialización. Uno de ellos es JSON\footnote{\href{http://json.org/}{http://json.org/}}. ``JSON'' significa ``JavaScript Object Notation - Notación de Objetos JavaScript'', pero no dejes que el nombre te engañe ---\codigo{JSON} está diseñado explícitamente para permitir su uso en diferentes lenguajes de programación.

Python 3 inclyye un módulo \codigo{json} en su librería estándar. Como el módulo \codigo{pickle}, el módulo \codigo{json} dispone de funciones para la serialización de estructuras de datos, almacenamiento de los datos serializados en disco, carga de los mismos y deserialización en un nuevo objeto Python. Pero también tiene importantes diferencias. La primera es que \codigo{JSON} es un formato de datos textual, no binario. La especificación \codigo{RFC 4627}\footnote{\href{http://www.ietf.org/rfc/rfc4627.txt}{http://www.ietf.org/rfc/rfc4627.txt}} define el formato y cómo se codifican los diferentes tipos de datos de forma textual. Por ejemplo, un valor booleano se almacena como la cadena texto de cinco caracteres \codigo{``false''} o como la cadena de texto de cuatro caracters \codigo{``true''}. Todos los valores de \codigo{JSON} tienen en cuenta las mayúsculas y minúsculas.

Segundo, como cualquier formato basado en texto, existe el problema de los espacios en blanco. \codigo{JSON} permite el uso de un número arbitrario de espacios en blanco (espacios, tabuladores, retornos de carro y saltos de línea) entre los valores. Estos espacios en blanco son ``no significativos'', lo que significa que los codificadores de \codigo{JSON} pueden añadir tantos como deseen, y los decodificadores de \codigo{JSON} están obligados a ignorarlos siempre que se encuentren entre dos valores. Esto permite que los datos de un fichero \codigo{JSON} se puedan imprimir bien formateados, anidando de forma clara los valores que se encuentran dentro de otros para que puedas verlos bien en un editor o visor de texto estándar. El módulo \codigo{json} de Python dispone de opciones para codificar la salida con formato apropiado para la lectura.

Tercero, existe el problema perenne de la codificación de caracteres. Puesto que \codigo{JSON} codifica los valores como texto plano, pero como ya sabes, no existe tal ``texto plano''. \codigo{JSON} debe almacenarse con caracteres Unicode (UTF-32, UTF-16 o, por defecto, UTF-8), y la sección 3 de la RFC-4627\footnote{\href{http://www.ietf.org/rfc/rfc4627.txt}{http://www.ietf.org/rfc/rfc4627.txt}}, define cómo indicar qué codificación se está utilizando. 

\section{Almacenamiento de datos en un fichero JSON}

\codigo{JSON} se parece mucho a una estructura de datos que pudieras definir en JavaScript. No es casualidad, en realidad, puedes utilizar la función \codigo{eval()} de JavaScript para ``decodificar'' los datos serializados en JSON. Lo fundamental es conocer que \codigo{JSON} forma parte del propio lenguaje JavaScript. Como tal, \codigo{JSON} puede que ya te sea familiar.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> basic_entry = {}
>>> basic_entry['id'] = 256
>>> basic_entry['title'] = 'Dive into history, 2009 edition'
>>> basic_entry['tags'] = ('diveintopython', 'docbook', 'html')
>>> basic_entry['published'] = True
>>> basic_entry['comments_link'] = None
>>> import json
>>> with open('basic.json', mode='w', encoding='utf-8') as f:
...     json.dump(basic_entry, f)
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 3:} Vamos a crear una nueva estructura de datos, en lugar de reutilizar la estructura de datos \codigo{entry} preexistente. Después veremos qué sucede cuando intentamos codificar en \codigo{JSON} la otra estructura de datos más compleja.

\item \emph{Línea 10:} \codigo{JSON} está basado en texto, lo que significa que es necesario abrir el fichero en modo texto y especificar una codificación de caracteres. Nunca te equivocarás utilizando \codigo{UTF-8}.

\item \emph{Línea 11:} Como con el módulo \codigo{pickle}, el módulo \codigo{json} define la función \codigo{dump()} que toma una estructura de datos Python y un objeto de flujo (stream) con permisos de escritura. La función \codigo{dump()} serializa la estructura de datos de Python y escribe el resultado en el objeto de flujo. Al hacerlo dentro de una sentencia \codigo{with} nos aseguramos de que el fichero quede cerrado correctamente cuando hayamos terminado.

\end{enumerate}

¿Cómo queda el resultado serializado?

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ cat basic.json
{"published": true, "tags": ["diveintopython", "docbook", "html"], 
"comments_link": null, "id": 256, 
"title": "Dive into history, 2009 edition"}
\end{lstlisting}
\end{minipage}

Es más legible que el fichero en formato de \codigo{pickle}. Pero como \codigo{JSON} puede contener tantos espacios en blanco como se desee entre diferentes valores, y el módulo \codigo{json} proporciona una forma sencilla de utilizar esta capacidad, podemos crear ficheros \codigo{JSON} aún más legibles.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
>>> shell
1
>>> with open('basic-pretty.json', mode='w', encoding='utf-8') as f:
...     json.dump(basic_entry, f, indent=2) 
\end{lstlisting}
\end{minipage}

Si se pasa el parámetro \codigo{indent} a la función \codigo{json.dump()} el fichero \codigo{JSON} resultante será más legible aún. A costa de un fichero de tamaño mayor. El parámetro \codigo{indent} es un valor entero en el que 0 significa ``pon cada valor en su propia línea'' y un número mayor que cero significa ``pon cada valor en su propia línea, y utiliza este número de espacios para indentar las estructuras de datos anidadas''.

Por lo que éste es el resultado:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ cat basic-pretty.json
{
  "published": true, 
  "tags": [
    "diveintopython", 
    "docbook", 
    "html"
  ], 
  "comments_link": null, 
  "id": 256, 
  "title": "Dive into history, 2009 edition"
}
\end{lstlisting}
\end{minipage}

\section{Mapeo de los tipos de datos de Python a JSON}

Puesto que \codigo{JSON} no es específico de Python, existen algunas diferencias en su cobertura de los tipos de dato de Python. Algunas de ellas son simplemente de denominación, pero existen dos tipos de dato importantes de Python que no existen en JSON. Observa esta tabla a ver si los echas de menos:

\begin{center}
\begin{tabular}{ | c | l | l |}
\hline
Notas & JSON & Python 3 \\ \hline
  & object & dictionary \\ \hline
  & array & list \\ \hline
  & string & string \\ \hline
  & integer & integer \\ \hline
  & real number & float \\ \hline
* & true & True \\ \hline
* & false & False \\ \hline
* & null & None \\ \hline 
\multicolumn{3}{| l |}{* Las mayúsculas y minúsculas en los valores \codigo{JSON} son significativas.} \\ \hline
\end{tabular}
\end{center}

¿Te has dado cuenta de lo que falta? ¡Tuplas y bytes! \codigo{JSON} tiene un tipo de datos array, al que se mapean las listas de Python, pero no tiene un tipo de datos separado para los ``arrays congelados'' (tuplas). Y aunque \codigo{JSON} soporta cadenas de texto, no tiene soporte para los objetos \codigo{bytes} o arrays de bytes.

\section{Serialización de tipos no soportados en JSON}

Incluso aunque \codigo{JSON} no tiene soporte intrínseco de bytes, es posible serializar objetos \codigo{bytes}. El módulo \codigo{json} proporciona unos puntos de extensibilidad para codificar y decodificar tipos de dato desconocidos (Por desconocido se entiende en este contexto a aquellos tipos de datos que no están definidos en la especificación de \codigo{JSON}). Si quieres codificar bytes u otros tipos de datos que \codigo{JSON} no soporte de forma nativa, necesitas proporcionar codificadores de decodificadores a medida para esos tipos de dato.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> entry
{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Dive into history, 2009 edition',
 'tags': ('diveintopython', 'docbook', 'html'),
 'article_link': 'http://diveintomark.org/archives/2009/03
                  /27/dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, 
                   tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, 
                   tm_wday=4, tm_yday=86, tm_isdst=-1),
 'published': True}
>>> import json
>>> with open('entry.json', 'w', encoding='utf-8') as f:
...     json.dump(entry, f)
... 
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "C:\Python31\lib\json\__init__.py", line 178, in dump
    for chunk in iterable:
  File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict
    for chunk in chunks:
  File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode
    o = _default(o)
  File "C:\Python31\lib\json\encoder.py", line 170, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: b'\xDE\xD5\xB4\xF8' is not JSON serializable
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 3:} Ok, es el momento de volver a la estructura de datos \codigo{entry}. Tiene de todo: un valor booleano, un \codigo{None}, una cadena de texto, una tupla de cadenas de texto, un objeto \codigo{bytes} y una estructura \codigo{time}.

\item \emph{Línea 15:} Sé lo que he dicho antes, pero vamos a repetirlo: \codigo{JSON} es un formato de texto. Siempre se deben abrir los ficheros \codigo{JSON} en modo texto con la codificación de caracteres \codigo{UTF-8}.

\item \emph{Línea 18:} ¡Error! ¿Qué ha pasado?

\end{enumerate}

Lo que ha pasado es que la función \codigo{json.dump()} intentó serializar el objeto \codigo{bytes} pero falló porque \codigo{JSON} no dispone de soporte de objetos \codigo{bytes}. Sin embargo, si es importante almacenar bytes en este formato, puedes definir tu propio ``formato de serialización''.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
def to_json(python_object):            
    if isinstance(python_object, bytes):
        return {'__class__': 'bytes',
                '__value__': list(python_object)}
    raise TypeError(repr(python_object) + ' is not JSON serializable')
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 1:} Para definir un formato de serialización para un tipo de datos que \codigo{JSON} no soporte de forma nativa, simplemente define una función que tome un objeto Python como parámetro. Este objeto será el que la función \codigo{json.dump()} sea incapaz de serializar de forma nativa ---en este caso el objeto \codigo{bytes}.

\item \emph{Línea 2:} La función debe validar el tipo de datos que recibe. No es estrictamente necesario pero así queda totalmente claro que casos cubre esta función, y hace más sencillo ampliarla más tarde.

\item \emph{Línea 4:} En este caso, he elegido convertir el objeto \codigo{bytes} en un diccionario. La clave \codigo{\_\_class\_\_} guardará el tipo de datos original (como una cadena, \codigo{``bytes''}), y la clave \codigo{\_\_value\_\_} guardará el valor real. Como hay que convertirlo a algo que pueda serializarse en JSON, no se puede guardar directamente el objeto \codigo{bytes}. Como un objeto bytes es una secuencia de números entereos; con cada entero entre el 0 y el 255, podemos utilizar la función \codigo{list()} para convertir el objeto \codigo{bytes} en una lista de enteros. De forma que el objeto \codigo{b'$\backslash$xDE$\backslash$xD5$\backslash$x84$\backslash$xF8'} se convierte en \codigo{[222, 213, 180, 248]}. Por ejemplo, el byte \codigo{$\backslash$xDE} en hexadecimal, se convierte en \codigo{222} en decimal, \codigo{$\backslash$xD5} es \codigo{213} y así cada uno de ellos.

\item \emph{Línea 5:} Esta línea es importante. La estructura de datos que estás serializando puede contener tipos de dato que ni el serializador interno del módulo de Python ni el tuyo puedan manejar. En este caso, tu serializador debe elevar una excepción \codigo{TypeError} para que la función \codigo{json.dump()} sepa que tu serializador no reconoció el tipo de dato del objeto.

\end{enumerate}

Y eso es todo, no necesitas hacer nada más. En particular, esta función a medida retorna un \emph{un diccionario de Python}, no una cadena. No estás haciendo la serialización a \codigo{JSON} completa por ti mismo; solamente la parte correspondiente a un tipo de datos que no está soportado de forma nativa. La función \codigo{json.dump()} hará el resto.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> import customserializer
>>> with open('entry.json', 'w', encoding='utf-8') as f:
...     json.dump(entry, f, default=customserializer.to_json)
... 
Traceback (most recent call last):
  File "<stdin>", line 9, in <module>
    json.dump(entry, f, default=customserializer.to_json)
  File "C:\Python31\lib\json\__init__.py", line 178, in dump
    for chunk in iterable:
  File "C:\Python31\lib\json\encoder.py", line 408, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "C:\Python31\lib\json\encoder.py", line 382, in _iterencode_dict
    for chunk in chunks:
  File "C:\Python31\lib\json\encoder.py", line 416, in _iterencode
    o = _default(o)
  File "/Users/pilgrim/diveintopython3/examples/customserializer.py", 
  line 12, in to_json
    raise TypeError(repr(python_object) + ' is not JSON serializable')
TypeError: time.struct_time(tm_year=2009, tm_mon=3, tm_mday=27, tm_hour=22,
 tm_min=20, tm_sec=42, tm_wday=4, tm_yday=86, tm_isdst=-1) 
 is not JSON serializable
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 3:} El módulo \codigo{customserializer} es el lugar en el que has definido la función \codigo{to\_json()} del ejemplo anterior.

\item \emph{Línea 4:} El modo texto, la codificación UTF-8, etc. ¡Lo olvidarás! ¡a veces lo olvidarás! Y todo funcionará hasta el momento en que falle, y cuando falle, lo hará de forma espectacular.

\item \emph{Línea 5:} Este es el trozo importante: asignar una función de conversión ad-hoc en la función \codigo{json.dump()}, hay que pasar tu función a la función \codigo{json.dump()} en el parámetro \codigo{default}.

\item \emph{Línea 20:} Ok, realmente no ha funcionado. Pero observa la excepción. La función \codigo{json.dump()} ya no se queja más sobre el objeto de tipo \codigo{bytes}. Ahora se está quejando sobre un objeto totalmente diferente, el objeto \codigo{time.struct\_time}.

\end{enumerate}

Aunque obtener una excepción diferente podría no parecer mucho progreso ¡lo es! Haremos una modificación más para superar este error:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
import time

def to_json(python_object):
    if isinstance(python_object, time.struct_time):
        return {'__class__': 'time.asctime',
                '__value__': time.asctime(python_object)}
    if isinstance(python_object, bytes):
        return {'__class__': 'bytes',
                '__value__': list(python_object)}
    raise TypeError(repr(python_object) + ' is not JSON serializable')
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 4:} Añadimos código a nuestra función \codigo{customserializer.to\_json()}, necesitamos validar que el objeto Python sea \codigo{time.struct\_time} (aquél con el que la función \codigo{json.dump()} está teniendo problemas).

\item \emph{Línea 6:} Haremos una conversión parecida a la que hicimos con el objeto \codigo{bytes}: convertir el objeto \codigo{time.struct\_time} en un diccionario que solamente contenga valores serializables en \codigo{JSON}. En este caso, la forma más sencilla de convertir una fecha/hora a \codigo{JSON} es convertirlo en una cadena con la función \codigo{time.asctime()}. La función \codigo{time.asctime()} convertirá la estructura en la cadena \codigo{'Fri Mar 27 22:20:42 2009'}.

\end{enumerate}

Con estas dos conversiones a medida, la estructura completa de datos \codigo{entry} debería serializarse a \codigo{JSON} sin más problemas.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> with open('entry.json', 'w', encoding='utf-8') as f:
...     json.dump(entry, f, default=customserializer.to_json)
... 
\end{lstlisting}
\end{minipage}

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=False]
you@localhost:~/diveintopython3/examples$ ls -l example.json
-rw-r--r-- 1 you  you  391 Aug  3 13:34 entry.json
you@localhost:~/diveintopython3/examples$ cat example.json
{"published_date": {"__class__": "time.asctime", 
"__value__": "Fri Mar 27 22:20:42 2009"},
"comments_link": null, "internal_id": {"__class__": "bytes", 
"__value__": [222, 213, 180, 248]},
"tags": ["diveintopython", "docbook", "html"], 
"title": "Dive into history, 2009 edition",
"article_link": "http://diveintomark.org/archives/
2009/03/27/dive-into-history-2009-edition",
"published": true}
\end{lstlisting}
\end{minipage}

\section{Carga de datos desde un fichero JSON}

Como el módulo \codigo{pickle}, el módulo \codigo{json} tiene una función \codigo{load()} que toma un objeto de flujo de datos y lee la información formateada en \codigo{JSON} y crea un objeto Python que es idéntico a la estructura de datos \codigo{JSON}.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
2
>>> del entry 
>>> entry
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'entry' is not defined
>>> import json
>>> with open('entry.json', 'r', encoding='utf-8') as f:
...     entry = json.load(f)
... 
>>> entry                  
{'comments_link': None,
 'internal_id': {'__class__': 'bytes', 
 '__value__': [222, 213, 180, 248]},
 'title': 'Dive into history, 2009 edition',
 'tags': ['diveintopython', 'docbook', 'html'],
 'article_link': 'http://diveintomark.org/archives/
2009/03/27/dive-into-history-2009-edition',
 'published_date': {'__class__': 'time.asctime', 
'__value__': 'Fri Mar 27 22:20:42 2009'},
 'published': True}
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 3:} Con fines demostrativos, pasamos a la consola \#2 y borramos la estructura de datos \codigo{entry} que habíamos creado antes con el módulo \codigo{pickle}.

\item \emph{Línea 10:} En el caso más simple, la función \codigo{json.load()} funciona de la misma forma que la función \codigo{pickle.load()}. Le pasamos un flujo de datos y devuelve un objeto Python nuevo.

\item \emph{Línea 12:} Tengo buenas y malas noticias. Las buenas primero: la función \codigo{json.load()} carga satisfactoriamente el fichero\codigo{entry.json} que has creado en la consola \#1 y crea un nuevo objeto Python que contiene la información. Ahora las malas noticias: No recrea la estructura de datos \codigo{entry} original. Los dos valores \codigo{'internal\_id'} y \codigo{'published\_date'} se han recreado como diccionarios ---específicamente, los diccionarios con valores compatibles \codigo{JSON} que creamos en la función de conversión \codigo{to\_json()}.

\end{enumerate}

La función \codigo{json.load()} no sabe nada sobre ninguna función de conversión que puedas haber pasado a la función \codigo{json.dump()}. Lo que se necesita es la función opuesta a \codigo{to\_json()} ---una función que tomará un objeto \codigo{JSON} convertido a medida y convertirá de nuevo a Python el tipo de datos original.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
# add this to customserializer.py
def from_json(json_object):
    if '__class__' in json_object:
        if json_object['__class__'] == 'time.asctime':
            return time.strptime(json_object['__value__'])
        if json_object['__class__'] == 'bytes':
            return bytes(json_object['__value__'])
    return json_object
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 2:} Esta función de conversión también toma un parámetro y devuelve un valor. Pero el parámetro que toma no es una cadena, es un objeto Python ---el resultado de deserializar la cadena \codigo{JSON} en un objeto Python.

\item \emph{Línea 3:} Lo único que hay que hacer es validar si el objeto contiene la clave \codigo{'\_\_class\_\_'} que creó la función \codigo{to\_json()}. Si es así, el valor de la clave \codigo{'\_\_class\_\_'} te dirá cómo decodificar el valor en su tipo de datos original de Python.

\item \emph{Línea 5:} Para decodificar la cadena de texto que que devolvió la función \codigo{time.asctime()}, utilizamos la función \codigo{time.strptime()}. Esta función toma una cadena de texto con formato de fecha y hora (en un formato que se puede adaptar, pero que tiene el formato por defecto de la función \codigo{time.asctime()}) y devuelve un objeto \codigo{time.struct\_time}.

\item \emph{Línea 7:} Para convertir de nuevo la lista de enteros a un objeto \codigo{bytes} puedes utilizar la función \codigo{bytes()}.

\end{enumerate}

Eso es todo. Solamente se manejaban dos tipos de dato en la función \codigo{to\_json()}, y ahora son esos dos tipos de dato los que se manejan en la función \codigo{from\_json()}. Este es el resultado:

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
2
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
...     entry = json.load(f, object_hook=customserializer.from_json)
... 
>>> entry                              
{'comments_link': None,
 'internal_id': b'\xDE\xD5\xB4\xF8',
 'title': 'Dive into history, 2009 edition',
 'tags': ['diveintopython', 'docbook', 'html'],
 'article_link': 'http://diveintomark.org/archives/
2009/03/27/dive-into-history-2009-edition',
 'published_date': time.struct_time(tm_year=2009, tm_mon=3, 
tm_mday=27, tm_hour=22, tm_min=20, tm_sec=42, tm_wday=4,
tm_yday=86, tm_isdst=-1),
 'published': True}
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 5:} Para utilizar la función \codigo{from\_json()} durante el proceso de deserialización, hay que pasarla en el parámetro \codigo{object\_hook} a la función \codigo{json.load()}. Una función que toma como parámetro a otra función ¡es muy útil!

\item \emph{Línea 7:} La estructura de datos \codigo{entry} ahora contiene uan clave \codigo{'internal\_id'} que tiene como valor a un objeto \codigo{bytes}. Y también contiene una clave \codigo{'published\_date'} cuyo valor es un objeto \codigo{time.struct\_time}.

\end{enumerate}

Sin embargo, aún queda un pequeño tema por tratar.

\noindent\begin{minipage}{\textwidth}
\begin{lstlisting}[mathescape=True]
>>> shell
1
>>> import customserializer
>>> with open('entry.json', 'r', encoding='utf-8') as f:
...     entry2 = json.load(f, object_hook=customserializer.from_json)
... 
>>> entry2 == entry
False
>>> entry['tags']  
('diveintopython', 'docbook', 'html')
>>> entry2['tags']
['diveintopython', 'docbook', 'html']
\end{lstlisting}
\end{minipage}

\begin{enumerate}

\item \emph{Línea 7:} Incluso después de utilizar la función \codigo{to\_json()} en la serialización y la función \codigo{from\_json()} en la deserialización, aún no hemos recreado la réplica perfecta de la estructura original ¿porqué no?

\item \emph{Línea 9:} En la estructura de datos original el valor de la clave \codigo{'tags'} era una tupla de tres cadenas.

\item \emph{Línea 11:} Pero en la estructura \codigo{entry2} el valor de la clave \codigo{'tags'} es una \emph{lista} de tres cadenas. \codigo{JSON} no distingue entre tuplas y listas; solamente tiene un tipo de datos parecido a la lista, el array, y el módulo \codigo{json} de Python convierte calladamente ambos tipos, listas y tuplas, en arrays de \codigo{JSON} durante la serialización. Para la mayoría de usos, es posible ignorar esta diferencia, pero conviene saberlo cuando se utiliza este módulo \codigo{json}.

\end{enumerate}

\section{Lecturas recomendadas}

\begin{quote}
Muchos artículos del módulo \codigo{pickle} hacen referencia a \codigo{cPickle}. En Python 2 existen dos implementaciones del módulo \codigo{pickle}, uno escrito en Python puro y otro escrito en C (pero que se puede llamar desde Python). En Python 3 se han consolidado ambos módulos, por lo que siempre deberías utilizar \codigo{import pickle}. 
\end{quote}

Sobre el módulo \codigo{pickle}:

\begin{itemize}

\item el módulo \codigo{pickle}: \newline
\href{http://docs.python.org/3.1/library/pickle.html}{http://docs.python.org/3.1/library/pickle.html}

\item \codigo{pickle} y \codigo{cPickle} ---serialización de objetos en Python: \newline
\href{http://www.doughellmann.com/PyMOTW/pickle/}{http://www.doughellmann.com/PyMOTW/pickle/}

\item Utilización de \codigo{pickle}: \newline
\href{http://wiki.python.org/moin/UsingPickle}{http://wiki.python.org/moin/UsingPickle}

\item Gestión de la persistencia en Python: \newline
\href{http://www.ibm.com/developerworks/library/l-pypers.html}{http://www.ibm.com/developerworks/library/l-pypers.html}

\end{itemize}

Sobre el módulo \codigo{json}:

\begin{itemize}

\item \codigo{json} --- Serializador de la notación de objetos de JavaScript: \newline
\href{http://www.doughellmann.com/PyMOTW/json/}{http://www.doughellmann.com/PyMOTW/json/}

\item Codificación y decodificación JSON en Python utilizando objetos a medida: \newline
\href{http://blog.quaternio.net/2009/07/16/json-encoding-and-decoding-with-custom-objects-in-python/}{http://blog.quaternio.net/2009/07/16/json-encoding-and-decoding-with-custom-objects-in-python/}

\end{itemize}

Sobre la extensibilidad de pickle:

\begin{itemize}

\item Sobre el almacenamiento de instancias de clase: \newline
\href{http://docs.python.org/3.1/library/pickle.html\#pickling-class-instances}{http://docs.python.org/3.1/library/pickle.html\#pickling-class-instances}

\item Persistencia de objetos externos: \newline
\href{http://docs.python.org/3.1/library/pickle.html\#persistence-of-external-objects}{http://docs.python.org/3.1/library/pickle.html\#persistence-of-external-objects}

\item Manejo de objetos con estado: \newline
\href{http://docs.python.org/3.1/library/pickle.html\#handling-stateful-objects}{http://docs.python.org/3.1/library/pickle.html\#handling-stateful-objects}

\end{itemize}
